const { Lexer } = require('./lexer');
const Parser = require('./parser');
const fs = require('fs')

class Interpreter {
    constructor() {
        this.environment = {};
        this.functions = {};
    }

    interpret(program) {
        for (const stmt of program) {
            let result = this.executeStatement(stmt);
            if(result){
                return result;
            }
        }
    }

    visitPropertyAccessExpression(expr) {
        const object = this.evaluateExpression(expr.object);
        const property = expr.property.value||expr.property.function.value;

        if (expr.property.function) {
            const args = expr.property.arguments.map(arg => this.evaluateExpression(arg));
            return object[property].call(object,...args);
        }

        return object[property];
    }

    executeStatement(stmt) {
        switch (stmt.type) {
            case 'LetStatement':
                this.environment[stmt.name.value] = this.evaluateExpression(stmt.value);
                break;
            case 'FunctionStatement':
                this.functions[stmt.name.value] = stmt;
                break;
            case 'ReturnStatement':
                this.environment['return'] = this.evaluateExpression(stmt.returnValue);
                return true;
            case 'ExpressionStatement':
                this.evaluateExpression(stmt.expression);
                break;
            case 'IfStatement':
                return this.executeIfStatement(stmt);
            case 'ForStatement':
                this.executeForStatement(stmt);
                break;
            case 'WhileStatement':
                this.executeWhileStatement(stmt);
                break;
            case 'SwitchStatement':
                this.executeSwitchStatement(stmt);
                break;
            case 'BlockStatement':
                return this.interpret(stmt.statements);
            case 'PrintStatement':
                this.executePrintStatement(stmt);
                break;
            case 'BreakStatement':
                return 'break';
            default:
                throw new Error(`Unknown statement type: ${stmt.type}`);
        }
    }

    executeSwitchStatement(stmt) {
        const value = this.evaluateExpression(stmt.expression);
        let matched = false;
        for (const c of stmt.cases) {
            let caseValue = c.isDefault?value:this.evaluateExpression(c.expression);
            if (matched || caseValue === value) {
                matched = true;
                this.executeStatement(c.body)
                if(c.break)break;
            }
        }

    }

    executeForStatement(stmt) {
        for (this.executeStatement(stmt.init); this.evaluateExpression(stmt.condition); this.evaluateExpression(stmt.increment)) {
            const result = this.interpret(stmt.body.statements);
            if(result === 'break')break
        }
    }

    executeWhileStatement(stmt) {
        while (this.evaluateExpression(stmt.condition)) {
            const result = this.interpret(stmt.body.statements);
            if(result === 'break')break;
        }
    }

    executeIfStatement(stmt) {
        const condition = this.evaluateExpression(stmt.condition);
        if (condition) {
            return this.interpret(stmt.consequence.statements);
        } else if (stmt.alternative) {
            return this.interpret(stmt.alternative.statements);
        }
    }

    executePrintStatement(stmt) {
        const args = stmt.arguments.map(arg => this.evaluateExpression(arg));
        console.log(...args);
    }

    evaluateExpression(expr) {
        if (!expr) {
            throw new Error('Expression is null');
        }

        switch (expr.type) {
            case 'PrefixExpression':
                return this.evaluatePrefixExpression(expr);
            case 'SuffixExpression':
                return this.evaluateSuffixExpression(expr);
            case 'Identifier':
                if(expr.value === 'Math'){
                    return Math;
                }
                return this.environment[expr.value];
            case 'IntegerLiteral':
                return expr.value;
            case 'BooleanLiteral':
                return expr.value;
            case 'StringLiteral':
                return expr.value;
            case 'ArrayLiteral':
                return expr.elements.map(element => this.evaluateExpression(element));
            case 'IndexExpression':
                const left = this.evaluateExpression(expr.left);
                const index = this.evaluateExpression(expr.index);
                return left[index];
                // if (Array.isArray(left)) {
                //     return left[index];
                // } else {
                //     throw new Error('Indexing is only supported on arrays');
                // }
            case 'InfixExpression':
                const leftInfix = this.evaluateExpression(expr.left);
                const rightInfix = this.evaluateExpression(expr.right);
                return this.evaluateInfixExpression(expr.operator, leftInfix, rightInfix);
            case 'GroupedExpression':
                return this.evaluateExpression(expr.expression);
            case 'CallExpression':
                return this.evaluateCallExpression(expr);
            case 'AssignExpression':
                return this.evaluateAssignExpression(expr);
            case 'PropertyAccessExpression':
                return this.visitPropertyAccessExpression(expr);
            case 'ObjectLiteral':
                return this.evaluateObjectLiteral(expr);
            case 'LambdaExpression':
                return this.evaluateLambdaExpression(expr);
            default:
                throw new Error(`Unknown expression type: ${expr.type}`);
        }
    }

    evaluateObjectLiteral(expr) {
        const obj = {};
        expr.properties.forEach(property => {
            const key = property.key.value;
            const value = this.evaluateExpression(property.value);
            obj[key] = value;
        });
        return obj;
    }

    evaluateAssignExpression(expr) {
        const value = this.evaluateExpression(expr.value);
        if (expr.name.type === 'Identifier') {
            this.environment[expr.name.value] = value;
        } else if (expr.name.type === 'IndexExpression') {
            const array = this.evaluateExpression(expr.name.left);
            const index = this.evaluateExpression(expr.name.index);
            if (Array.isArray(array)) {
                array[index] = value;
            } else {
                throw new Error('Indexing is only supported on arrays');
            }
        }else if (expr.name.type === 'PropertyAccessExpression') {
            const object = this.evaluateExpression(expr.name.object);
            const property = expr.name.property.value;
            object[property] = value;
        }

        return value;
    }

    evaluatePrefixExpression(expr) {
        const operator = expr.operator;
        const right = this.evaluateExpression(expr.right);
        switch (operator) {
            case '~':
                return ~right;
            case '+':
                return right;
            case '-':
                return -right;
            case '++':
                this.environment[expr.right.value]=right+1;
                return right + 1;
            case '--':
                this.environment[expr.right.value]=right-1;
                return right - 1;
            case '!':
                return !right;
            default:
                throw new Error(`Unknown operator: ${operator}`);
            // ...existing code...
        }
    }

    evaluateInfixExpression(operator, left, right) {
        switch (operator) {
            case '+':
                return left + right;
            case '-':
                return left - right;
            case '*':
                return left * right;
            case '/':
                return left / right;
            case '>':
                return left > right;
            case '<':
                return left < right;
            case '<=':
                return left <= right;
            case '>=':
                return left >= right;
            case '&':
                return left & right;
            case '|':
                return left | right;
            case '^':
                return left ^ right;
            case '%':
                return parseInt(left % right);
            case '<<':
                return left << right;
            case '>>':
                return left >> right;
            case '==':
                return left === right;
            case '~':
                return ~right;
            case '!=':
                return left !== right;
            case '&&':
                return left && right;
            case '||':
                return left || right;
            default:
                throw new Error(`Unknown operator: ${operator}`);
        }
    }

    evaluateCallExpression(expr) {
        const func = this.evaluateExpression(expr.function);
        const args = expr.arguments.map(arg => this.evaluateExpression(arg));

        if (typeof func === 'function') {
            return func(...args);
        }

        const funcDef = this.functions[expr.function.value];
        if (!funcDef) {
            throw new Error(`Undefined function: ${expr.function.value}`);
        }

        const localEnv = { ...this.environment };
        funcDef.parameters.forEach((param, index) => {
            localEnv[param.value] = args[index];
        });

        const interpreter = new Interpreter();
        interpreter.environment = localEnv;
        interpreter.functions = this.functions;

        interpreter.interpret(funcDef.body.statements);

        return interpreter.environment['return'];
    }

    evaluateSuffixExpression(expr) {
        const left = this.evaluateExpression(expr.left);
        const operator = expr.operator;
        switch (operator) {
            case '++':
                this.environment[expr.left.value]=left+1;
                return left;
            case '--':
                this.environment[expr.left.value]=left-1;
                return left;
        }
    }

    evaluateLambdaExpression(expr) {
        const lambda = (...args) => {
            const localEnv = { ...this.environment };
            expr.parameters.forEach((param, index) => {
                localEnv[param.value] = args[index];
            });

            const interpreter = new Interpreter();
            interpreter.environment = localEnv;
            interpreter.functions = this.functions;

            interpreter.interpret(expr.body.statements);

            return interpreter.environment['return'];
        };

        return lambda;
    }
}

// Example usage:
let code = `
    fn greet(name) {
        print("Hello, " + name + "!");
    }
    
    for(let i = 0; i < 100000; i=i+1) {
        let X = 1;
    }
    
    
    greet("Alice");
`;

let arguments = process.argv.slice(2);
let filename = arguments[0]||'test.j';

code = fs.readFileSync(filename, 'utf8');

const lexer = new Lexer(code);
const parser = new Parser(lexer);
const program = parser.parseProgram();

const interpreter = new Interpreter();

interpreter.interpret(program);
console.log("----program running finished------");
console.log("environment variables:")
console.log(interpreter.environment);
let x = 5;
console.log(x+++1);


